using System;
using System.IO;
using System.Threading;
using NLog;

namespace TeamCityConfigurationMonitor.Core
{
    class ChangeNotifier : IChangeNotifier, IDisposable
    {
        private readonly IConfiguration configuration;
        private readonly Action onConfigurationChange;
        private readonly IChangeProvider changeProvider;
        private Timer timer;
        private FileSystemWatcher watcher;
        private readonly Logger logger = LogManager.GetCurrentClassLogger();

        public ChangeNotifier(IConfiguration configuration, Action onConfigurationChange, IChangeProvider changeProvider)
        {
            this.configuration = configuration;
            this.onConfigurationChange = onConfigurationChange;
            this.changeProvider = changeProvider;
        }

        public void StartWatching()
        {
            timer = new Timer(delegate { onConfigurationChange(); }, null, Timeout.Infinite, Timeout.Infinite);

            watcher = new FileSystemWatcher(configuration.ConfigurationLocation, configuration.FilesToMonitor)
            {
                IncludeSubdirectories = true,
                NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.LastWrite
            };

            watcher.Changed += ShiftTimerAndCollectChange;
            watcher.Created += ShiftTimerAndCollectChange;
            watcher.Deleted += ShiftTimerAndCollectChange;
            watcher.Renamed += ShiftTimerAndCollectChange;
            watcher.Error += WatcherOnError;

            watcher.EnableRaisingEvents = true;

            logger.Info("Started monitoring configuration directory for changes");
        }

        private static void WatcherOnError(object sender, ErrorEventArgs errorEventArgs)
        {
            throw errorEventArgs.GetException();
        }

        private void ShiftTimer()
        {
            logger.Trace("Shifting notification forward {0} seconds", configuration.ChangeNotificationTimeoutInSeconds);
            timer.Change(configuration.ChangeNotificationTimeoutInSeconds * 1000, Timeout.Infinite);
        }

        public void NotifyLater()
        {
            ShiftTimer();
        }

        private void ShiftTimerAndCollectChange(object sender, FileSystemEventArgs e)
        {
            ShiftTimer();

            logger.Debug("Found change of type {0} in file {1}", e.ChangeType, e.FullPath);
            changeProvider.CollectChange(new FileSystemChange(e.ChangeType, e.FullPath, e.Name));
        }

        public void Dispose()
        {
            logger.Debug("Stopped monitoring configuration directory for changes");
            timer.Dispose();
            watcher.Dispose();
        }
    }
}